#! /usr/bin/env python

"""Extract configuration items into various configuration headers.

This uses the configitems file, a database consisting of text lines with the
following single-tab-separated fields:
 - Name of the configuration item, e.g. PQXX_HAVE_PTRDIFF_T.
 - Publication marker: public or internal.
 - A single environmental factor determining the item, e.g. libpq or compiler.
"""

from __future__ import (
    absolute_import,
    print_function,
    unicode_literals,
    )

__metaclass__ = type

import os.path
import re
import sys

usage = """\
Usage: splitconfig [srcdir]

where [srcdir] is the main libpqxx source directory containing the configitems
file, the include directory etc.  This defaults to the current directory.
"""


def help_and_exit(error_string=None, ok=True):
    if error_string is not None:
        sys.stderr.write(error_string + '\n')
    print(usage)
    if ok:
        sys.exit(0)
    else:
        sys.exit(1)


def read_configitems(filename):
    """Read the configuration-items database.

    :param filename: Path to the configitems file.
    :return: Sequence of text lines from configitems file.
    """
    text = open(filename, 'rb').read().decode('ascii')
    return [line.split() for line in text.splitlines()]


def map_configitems(items):
    """Map each config item to publication/factor.

    :param items: Sequence of config items: (name, publication, factor).
    :return: Dict mapping each item name to a tuple (publication, factor).
    """
    return dict(
        (item, (publication, factor))
        for item, publication, factor in items)


def read_header(filename):
    """Read the original config.h generated by autoconf.

    :param filename: Path to the config.h file.
    :return: Sequence of text lines from config.h.
    """
    return open(filename, 'rb').read().decode('ascii').splitlines()


def extract_macro_name(config_line):
    """Extract a cpp macro name from a configuration line.

    :param config_line: Text line from config.h which may define a macro.
    :return: Name of macro defined in `config_line` if it is a `#define`
        statement, or None.
    """
    config_line = config_line.strip()
    match = re.match('\s*#\s*define\s+([^\s]+)', config_line)
    if match is None:
        return None
    else:
        return match.group(1)


def extract_section(header_lines, items, publication, factor):
    """Extract config items for given publication/factor from header lines.

    :param header_lines: Sequence of header lines from config.h.
    :param items: Dict mapping macro names to (publication, factor).
    :param publication: Extract only macros for this publication tag.
    :param factor: Extract only macros for this environmental factor.
    :return: Sequence of `#define` lines from `header_lines` insofar they
        fall within the requested section.
    """
    return sorted(
        line.strip()
        for line in header_lines
        if items.get(extract_macro_name(line)) == (publication, factor)
        )


def generate_config(header_lines, items, publication, factor):
    """Generate config file for a given section.  Skip empty ones.

    :param header_lines: Sequence of header lines from config.h.
    :param items: Dict mapping macro names to (publication, factor).
    :param publication: Extract only macros for this publication tag.
    :param factor: Extract only macros for this environmental factor.
    """
    config_file = "include/pqxx/config-%s-%s.h" % (publication, factor)
    section = extract_section(header_lines, items, publication, factor)
    if len(section) == 0:
        print("Generating %s: no items--skipping." % config_file)
        return
    with open(config_file, 'wb') as header:
        header.write(
            "/* Automatically generated from config.h: %s/%s config. */\n"
            % (publication, factor))
        header.write('\n'.join(section))
        header.write('\n')
    print("Generating %s: %d item(s)." % (config_file, len(section)))


def check_args(argv):
    if len(argv) > 2:
        help_and_exit("Too many arguments.", ok=False)

    if len(argv) == 2:
        if argv[1] in ('-h', '--help'):
            help_and_exit()
        if not os.path.isdir(argv[1]):
            help_and_exit("%s is not a directory." % srcdir, ok=False)


def get_base_dir(argv):
    if len(argv) > 1:
        return argv[1]
    else:
        return "."


if __name__ == '__main__':
    check_args(sys.argv)
    srcdir = get_base_dir(sys.argv)
    items = read_configitems(os.path.join(srcdir, "configitems"))
    config = map_configitems(items)
    publications = sorted(set(item[1] for item in items))
    factors = sorted(set(item[2] for item in items))
    original_header = read_header("include/pqxx/config.h")
    items_map = map_configitems(items)

    for publication in publications:
        for factor in factors:
            generate_config(original_header, items_map, publication, factor)
